//#########################################################################
//                              WackiEngie
//           Copyright (c) 2014 Marc Wacker. All rights reserved.
//#########################################################################

#pragma once

#include "WeCorePrerequisites.h"

namespace WackiEngine
{

	class Texture
	{
	public:
		~Texture();

		// accessors
		GLuint		handle() const;
		GLenum		target() const;
		GLint		format() const;
		GLsizei		width() const;
		GLsizei		height() const;
		GLsizei		depth() const;
		GLfloat		texelWidth() const;
		GLfloat		texelHeight() const;
		GLfloat		texelDepth() const;

		GLenum		minFilter() const;
		GLenum		magFilter() const;

		void		setMinFilter(GLenum);
		void		setMagFilter(GLenum);

		GLfloat		minLod() const;
		GLfloat		maxLod() const;

		void		setMinLod(GLfloat);
		void		setMaxLod(GLfloat);
		
		GLint		baseLevel() const;
		GLint		maxLevel() const;

		void		setBaseLevel(GLint);
		void		setMaxLevel(GLint);

		GLenum		wrapS() const;
		GLenum		wrapT() const;
		GLenum		wrapR() const;

		// set set GL_TEXTURE_WRAP_S, invokes glTextureParameteri()
		void		setWrapS(GLenum);
		// set set GL_TEXTURE_WRAP_T, invokes glTextureParameteri()
		void		setWrapT(GLenum);
		// set set GL_TEXTURE_WRAP_R, invokes glTextureParameteri()
		void		setWrapR(GLenum);

		// set GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T and GL_TEXTURE_WRAP_R to a single value
		void		setWrap(GLenum);

		GLenum		compareMode() const;
		GLenum		compareFunc() const;

		void		setCompareMode(GLenum);
		void		setCompareFunc(GLenum);

		// services
		void		bind() const;

		static void bindNone(GLenum target);

		void		generateMipmap();

	protected:
		// direct instantiation of base class isn't allowed but derived classes are able to
		Texture();

		GLuint		_handle;
		GLenum		_target;
		GLint		_format;

		GLsizei		_size[3];
		GLfloat		_texelSize[3];

		void		init(GLenum target);
		void		updateParameters();

	private:
		explicit Texture(const Texture&);
		Texture& operator=(const Texture&);
	};

	inline Texture::~Texture()
	{
		if(_handle != GL_ZERO)
			glDeleteTextures(1, &_handle);
	}

	inline GLuint Texture::handle() const
	{
		return _handle;
	}
	
	inline GLenum Texture::target() const
	{
		return _target;
	}

	inline GLint Texture::format() const
	{
		return _format;
	}

	inline GLsizei Texture::width() const
	{
		return _size[0];
	}

	inline GLsizei Texture::height() const
	{
		return _size[1];
	}

	inline GLsizei Texture::depth() const
	{
		return _size[2];
	}

	inline GLfloat Texture::texelWidth() const
	{
		return _texelSize[0];
	}

	inline GLfloat Texture::texelHeight() const
	{
		return _texelSize[1];
	}

	inline GLfloat Texture::texelDepth() const
	{
		return _texelSize[2];
	}


	inline GLenum Texture::minFilter() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_MIN_FILTER, &result);

		return (GLenum)result;
	}

	inline GLenum Texture::magFilter() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_MAG_FILTER, &result);

		return (GLenum)result;
	}


	inline void Texture::setMinFilter(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_NEAREST) ||
				(v == GL_LINEAR) ||
				(v == GL_NEAREST_MIPMAP_NEAREST) ||
				(v == GL_LINEAR_MIPMAP_NEAREST) ||
				(v == GL_NEAREST_MIPMAP_LINEAR) ||
				(v == GL_LINEAR_MIPMAP_LINEAR));

		glTexParameteri(_target, GL_TEXTURE_MIN_FILTER, (GLint) v);
	}

	inline void Texture::setMagFilter(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_NEAREST) ||
				(v == GL_LINEAR));

		glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, (GLint) v);
	}


	inline GLfloat Texture::minLod() const
	{
		assert(_handle != GL_ZERO);
		GLfloat result;
		glGetTexParameterfv(_target, GL_TEXTURE_MIN_LOD, &result);
		return result;
	}

	inline GLfloat Texture::maxLod() const
	{
		assert(_handle != GL_ZERO);
		GLfloat result;
		glGetTexParameterfv(_target, GL_TEXTURE_MAX_LOD, &result);
		return result;
	}

	inline void Texture::setMinLod(GLfloat v)
	{
		assert(_handle != GL_ZERO);
		glTexParameterf(_target, GL_TEXTURE_MIN_LOD, v);
	}

	inline void Texture::setMaxLod(GLfloat v)
	{
		assert(_handle != GL_ZERO);
		glTexParameterf(_target, GL_TEXTURE_MAX_LOD, v);
	}
	
	inline GLint Texture::baseLevel() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_BASE_LEVEL, &result);
		return result;
	}

	inline GLint Texture::maxLevel() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_MAX_LEVEL, &result);
		return result;
	}
	
	inline void Texture::setBaseLevel(GLint v)
	{
		assert(_handle != GL_ZERO);
		glTexParameteri(_target, GL_TEXTURE_BASE_LEVEL, v);
	}

	inline void Texture::setMaxLevel(GLint v)
	{
		assert(_handle != GL_ZERO);
		glTexParameteri(_target, GL_TEXTURE_MAX_LEVEL, v);
	}

	inline GLenum Texture::wrapS() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_WRAP_S, &result);
		return (GLenum) result;
	}

	inline GLenum Texture::wrapT() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_WRAP_T, &result);
		return (GLenum) result;
	}

	inline GLenum Texture::wrapR() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_WRAP_R, &result);
		return (GLenum) result;
	}


	inline void Texture::setWrapS(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_CLAMP_TO_EDGE) ||
				(v == GL_MIRRORED_REPEAT) ||
				(v == GL_REPEAT) ||
				(v == GL_CLAMP_TO_BORDER));
		glTexParameteri(_target, GL_TEXTURE_WRAP_S, (GLint) v);
	}

	inline void Texture::setWrapT(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_CLAMP_TO_EDGE) ||
				(v == GL_MIRRORED_REPEAT) ||
				(v == GL_REPEAT) ||
				(v == GL_CLAMP_TO_BORDER));
		glTexParameteri(_target, GL_TEXTURE_WRAP_T, (GLint) v);
	}

	inline void Texture::setWrapR(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_CLAMP_TO_EDGE) ||
				(v == GL_MIRRORED_REPEAT) ||
				(v == GL_REPEAT) ||
				(v == GL_CLAMP_TO_BORDER));
		glTexParameteri(_target, GL_TEXTURE_WRAP_R, (GLint) v);
	}

	inline void Texture::setWrap(GLenum v)
	{
		setWrapS(v);
		setWrapT(v);
		setWrapR(v);
	}

	inline GLenum Texture::compareMode() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_COMPARE_MODE, &result);
		return (GLenum) result;
	}

	inline GLenum Texture::compareFunc() const
	{
		assert(_handle != GL_ZERO);
		GLint result;
		glGetTexParameteriv(_target, GL_TEXTURE_COMPARE_FUNC, &result);
		return (GLenum) result;
	}


	inline void Texture::setCompareMode(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_COMPARE_REF_TO_TEXTURE) ||
				(v == GL_NONE));
		glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, (GLint) v);
	}

	inline void Texture::setCompareFunc(GLenum v)
	{
		assert(_handle != GL_ZERO);
		assert(	(v == GL_LEQUAL) ||
				(v == GL_GEQUAL) ||
				(v == GL_LESS) ||
				(v == GL_GREATER) ||
				(v == GL_EQUAL) ||
				(v == GL_NOTEQUAL) ||
				(v == GL_ALWAYS) ||
				(v == GL_NEVER));
		glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, (GLint) v);
	}


	// services
	inline void Texture::bind() const
	{
		assert(_handle != GL_ZERO);
		glBindTexture(_target, _handle);
	}


	inline void Texture::bindNone(GLenum target)
	{
		glBindTexture(target, 0);
	}

	inline Texture::Texture()
		: _handle(GL_ZERO), 
		_target(GL_NONE), 
		_format(GL_NONE)
	{
	}

}